/*
 * Copyright (c) 2024 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "ads78.h"
#include <mc/exception.h>

using namespace dev;

// ADS78 Channel to Register Mapping
static constexpr uint8_t CHANNEL_REGISTER_MAP[][2] = {
    {0, 8},  // Channel 0 -> Register 8
    {1, 12}, // Channel 1 -> Register 12
    {2, 9},  // Channel 2 -> Register 9
    {3, 13}, // Channel 3 -> Register 13
    {4, 10}, // Channel 4 -> Register 10
    {5, 14}, // Channel 5 -> Register 14
    {6, 11}, // Channel 6 -> Register 11
    {7, 15}  // Channel 7 -> Register 15
};

uint32_t chip_ads78::get_register_address(uint32_t channel_offset) {
    if (channel_offset > MAX_OFFSET_INDEX) {
        return HAS_OK; // Invalid channel
    }
    return (CHANNEL_REGISTER_MAP[channel_offset][1] << REGISTER_SHIFT_BITS) | REGISTER_CMD_MASK;
}

uint16_t chip_ads78::convert_adc_to_millivolts(uint16_t adc_value) {
    // Convert 12-bit ADC value to millivolts using reference voltage
    // Formula: voltage_mv = (adc_value * reference_voltage_mv) / adc_max_value
    return static_cast<uint16_t>((static_cast<uint32_t>(adc_value) * ADC_REFERENCE_VOLTAGE_MV) /
                                 ADC_MAX_VALUE);
}

bool chip_ads78::init(mc::mutable_dict& csr_object) {
    try {
        chip_base::init(csr_object);
    } catch (const std::exception& e) {
        elog("ADS78 configuration loading failed: ${error}", ("error", e.what()));
        return false;
    }

    return true;
}

int32_t chip_ads78::bit_read(uint32_t offset, uint8_t length, uint32_t mask,
                              std::vector<uint8_t>& in_buffer, std::vector<uint8_t>& out_buffer,
                              std::string& error) {
    if (!m_host_bus || !m_left_bus) {
        error = "pre bus object not found";
        MC_THROW(mc::invalid_op_exception, error);
    }

    if (offset > MAX_OFFSET_INDEX) {
        error = "ADS78 invalid channel offset: offset=" + std::to_string(offset) + ", max_allowed=" + std::to_string(MAX_OFFSET_INDEX);
        elog("${error}", ("error", error));
        return HAS_ERR;
    }

    try {
        uint32_t register_address = get_register_address(offset);
        if (register_address == HAS_OK) {
            error = "ADS78 failed to get register address for channel: " + std::to_string(offset);
            elog("${error}", ("error", error));
            return HAS_ERR;
        }

        std::string read_error;
        int32_t read_result =
            chip_base::block_read(register_address, length, in_buffer, out_buffer, read_error);
        if (read_result != HAS_OK) {
            error = "ADS78 read operation failed: channel=" + std::to_string(offset) + ", address=" + std::to_string(register_address) + ", length=" + std::to_string(length) + ", error=" + read_error;
            elog("${error}", ("error", error));
            return read_result;
        }

        if (out_buffer.size() < BYTES_PER_REGISTER) {
            error = "ADS78 insufficient data received: expected=" + std::to_string(BYTES_PER_REGISTER) + ", actual=" + std::to_string(out_buffer.size());
            elog("${error}", ("error", error));
            return HAS_ERR;
        }

        uint16_t raw_adc_value =
            (static_cast<uint16_t>(out_buffer[0]) & 0xf) << BITS_PER_BYTE | out_buffer[1];

        uint16_t voltage_millivolts = convert_adc_to_millivolts(raw_adc_value);

        out_buffer[0] = static_cast<uint8_t>((voltage_millivolts >> BITS_PER_BYTE) & 0xFF);
        out_buffer[1] = static_cast<uint8_t>(voltage_millivolts & 0xFF);

        return HAS_OK;
    } catch (const std::exception& e) {
        error = std::string("ADS78 bit read operation failed: channel=") + std::to_string(offset) + ", length=" + std::to_string(length) + ", error=" + e.what();
        elog("${error}", ("error", error));
        return HAS_ERR;
    }
}
